Repository navigation
Upgrading
Version constraints, how the application itself gets upgraded, and which variable changes are more expensive than they look.
| Component | Constraint | Declared in |
|---|---|---|
| Terraform |
>= 1.5 declared; 1.7 or later needed for the removed block |
versions.tf, main.tf
|
hashicorp/azurerm |
~> 4.33 |
versions.tf, and again in modules/key_vault, modules/auth, modules/maintenance
|
hashicorp/azuread |
~> 2.0 at the root, ~> 2.47 in modules/auth
|
versions.tf, modules/auth/main.tf
|
hashicorp/random |
~> 3.1 |
versions.tf, modules/key_vault, modules/auth
|
hashicorp/null |
~> 3.1 |
versions.tf |
hashicorp/time |
~> 0.9 |
modules/auth/main.tf |
The effective azuread constraint is the intersection, so ~> 2.47. The
~> on a two-part version means minor upgrades within 4.x and 2.x are
accepted; a 5.x azurerm or 3.x azuread requires editing the constraints.
.terraform.lock.hcl is gitignored, so provider versions are not pinned across
machines. Two operators can end up on different provider patch versions. If you
run this from CI, commit a lock file in your own fork or pin exact versions.
The running container images are not upgraded by terraform apply. Both
container apps declare:
lifecycle {
ignore_changes = [template[0].container[0].image]
}The deploy pipeline owns the image, pushing an immutable tag with
az containerapp update. Bumping frontend_image_tag or api_image_tag in
tfvars changes nothing on an existing deployment — those variables only take
effect the first time a container app is created. Roll a new version through
your pipeline, not through Terraform.
The same is true of the maintenance job's image, which is hardcoded to
<registry>/reportmate-maintenance:latest.
Terraform creates the database and stops there. The schemas/ directory holds
the numbered migration SQL and two runners (run-migrations.ps1,
run_migration.py), and the API applies migrations at startup. An infrastructure
apply is not a schema upgrade and a schema upgrade is not an infrastructure
apply — sequence them yourself.
| Change | Consequence |
|---|---|
db_geo_redundant_backup |
Create-time only on PostgreSQL Flexible Server. Changing it is a rebuild, which prevent_destroy blocks |
postgres_server_name |
Renames the server, i.e. replaces it. Blocked by prevent_destroy
|
db_name |
Replaces the database. Blocked by prevent_destroy
|
db_max_connections |
Static Postgres parameter. Applying it restarts the server — do it in a maintenance window, not alongside an unrelated change |
db_storage_mb |
Effectively inert after create; it is in ignore_changes. Storage can only grow, and auto-grow does the growing |
db_sku_name |
Scales the server in place, with a brief outage |
function_app_sku |
The plan is named <app>-flex-plan. Y1 to FC1 is not an in-place SKU change, so a deployment that holds an older <app>-plan gets a fresh plan and the old one destroyed |
storage_account_name |
Replaces the storage account, losing the ingest queue |
key_vault_name |
Replaces the vault. Purge protection means the old name is unusable for the soft-delete retention period |
container_environment_name |
Replaces the Container Apps environment and therefore both container apps |
enable_custom_domain to false |
Destroys the Front Door profile and the managed certificate. Re-enabling means re-validating the domain |
api_db_pool_max * api_max_replicas must stay at or below
db_max_connections minus the containers module's db_connection_reserve
(default 15). The API container app enforces this with a precondition, so
getting it wrong fails the plan rather than the fleet.
To scale the API out, raise db_max_connections first — in its own apply,
accepting the server restart — and only then raise api_max_replicas or
api_db_pool_max. The precondition compares variable values only, so a single
apply that raises both passes it, while the API can scale out against the old
ceiling before the server restart completes.
Note that api_db_pool_max also sets the API's HTTP scale rule concurrency, so
changing it moves the point at which a second replica spins up.
enable_oidc_api defaults to false and the whole api.tf file is a no-op until
it is true. Enabling it in one apply:
- Creates a second Entra application and service principal.
- Grants the roles listed in
oidc_reader_principal_ids,oidc_ingest_principal_idsandoidc_admin_principal_ids. - Sets
ENABLE_OIDC_AUTH=trueplus the issuer and audience on the API container, which triggers a new revision.
Because app_role_assignment_required is true on the API service principal,
nothing can obtain a token until you have populated at least one of the three
principal lists. Assign the roles in the same apply that flips the flag.
-
terraform init -upgradewith your backend config. -
terraform planand read every replacement carefully. Anything touching the PostgreSQL server or database will fail onprevent_destroyrather than proceed — that is the intended behaviour, not a bug to work around. - Expect the identifier-URI
null_resources to always show as changing. - If the existing deployment includes a Web PubSub service from an older
module version, the plan destroys it together with its role assignment and
the
webpubsub-connection-stringvault secret. The current module has no messaging module, and nothing reads those resources. - Apply, then confirm the running image tags did not move. If they did, the
ignore_changesblocks were removed and the apply rolled the running image back to the tfvars value.
MIT licensed — reportmate.app