Data Quality Detect. Quantify. Resolve.
← Back to download

AltitudeIQ Pre-Install Checklist

Audience: your Azure / Cloud Ops team. Run through this before the scheduled install window (ideally 2–3 business days ahead) so anything that needs a ticket filed has time to clear.

Target: one Azure subscription, one region of your choice.

Installer: Zero-dependency Go binary run from any machine with az access to the target subscription.

Each item below lists: what to check, how to check, pass criteria, and if it fails, what to do. Items are ordered by likelihood of being a blocker in a typical enterprise Azure tenant.


1. Subscription + RBAC

1.0 Target subscription designated for AltitudeIQ

Check: your Azure / Cloud Ops team has designated which Azure subscription will host AltitudeIQ. This can be an existing subscription with a dedicated resource group, or a net-new subscription stood up specifically for this install.

Pass: a subscription has been agreed by your cloud governance team and the subscription ID is in hand.

Fail (no subscription yet): if your tenant doesn't already have a suitable subscription, your procurement / EA team needs to provision one through the Microsoft EA portal (or your Cloud Solution Provider partner) before install day. New EA subscription provisioning typically takes 1–5 business days, so file the request at least a week ahead of the agreed install window.

Recommendation: a dedicated subscription gives the cleanest operational boundary for AltitudeIQ — distinct cost reporting, simpler RBAC scoping, and a one-command teardown path (az group delete against the install RG, no risk of touching neighbouring workloads). A shared subscription with a dedicated resource group is also fine; the installer scopes everything to the RG it creates.

1.1 Target subscription is named and accessible

Check: you know the exact subscription ID that will host AltitudeIQ, and you can list resource groups in it.

az account show --subscription <SUB_ID> --query '{name:name, id:id, tenantId:tenantId}' -o json
az group list --subscription <SUB_ID> -o table | head -5

Pass: subscription name and ID match expectations; az group list returns without a permission error.

Fail: you may be signed into the wrong tenant (az login --tenant <TENANT_ID>) or missing at least Reader role on the subscription.

1.2 Installer-running user has Owner (or Contributor + User Access Administrator) at the subscription scope

The installer creates a new resource group and grants a user-assigned managed identity two roles on that RG: Contributor (to provision resources) and Role Based Access Control Administrator (to grant the Container Apps environment AcrPull on the customer's ACR at runtime). That second role assignment requires the installer to hold Owner OR User Access Administrator at the subscription scope at run time.

az role assignment list \
  --subscription <SUB_ID> \
  --assignee <YOUR_AAD_UPN> \
  --all \
  --query "[?scope=='/subscriptions/<SUB_ID>'].{role:roleDefinitionName, scope:scope}" -o table

Pass: Owner OR (Contributor + User Access Administrator) at the subscription scope.

Fail: ask your Azure admin to grant one of the above for the duration of the install window. After install completes, you can downgrade to Reader; the managed identity owns the RG-scoped operations going forward.

1.3 No subscription-level deny-assignment blocks our scope

Large org subscriptions sometimes carry deny assignments from Azure Blueprints or Management Group policy that override role assignments.

az deployment sub list --subscription <SUB_ID> --query "[?contains(name, 'denyAssignment')]" -o table
az lock list --subscription <SUB_ID> -o table | head

Pass: no deny assignments that block Microsoft.Resources/resourceGroups/write, Microsoft.Authorization/roleAssignments/write, or Microsoft.App/*.

Fail: open a ticket with the Azure policy team. We can work around most deny assignments by targeting a specific subscription they've already approved, but the scope has to be provided explicitly.


2. Azure Policy

2.1 No policy blocks our resource types in the target region

AltitudeIQ provisions:

Check:

az policy state list --subscription <SUB_ID> --filter "complianceState eq 'NonCompliant'" --query "[].{policy:policyDefinitionName, resource:resourceType}" -o table | head -20
az policy assignment list --subscription <SUB_ID> --query "[].{name:displayName, scope:scope}" -o table

Pass: no deny policies on the resource types above for region East US 2 (or the agreed region).

Fail: usually fixable by either (a) adding an exemption for the AltitudeIQ RG once it exists, or (b) adjusting the Bicep parameters to match policy requirements (e.g. if policy requires tags, we parameterise tags).

2.2 No policy blocks public ingress on Container Apps

The dashboard, backend API, and frontend are served from Container Apps with public ingress (HTTPS-only, TLS terminated by Azure). Some tenants have a policy blocking public ingress on Container Apps or requiring VNet integration.

Check:

az policy assignment list --subscription <SUB_ID> \
  --query "[?contains(displayName, 'Container Apps') || contains(displayName, 'ingress') || contains(displayName, 'Ingress')].{name:displayName, enforcement:enforcementMode}" \
  -o table

Pass: either public ingress is allowed (no matching policies returned), OR you have a private-endpoint pattern we can adapt to (in which case please flag so we can plan).

Fail for Starter / Professional: Starter and Professional use public ingress by default. If policy requires private-only, escalate to the Enterprise tier conversation — it has VNet integration and private ingress.

2.3 Key Vault purge protection policy is compatible

AltitudeIQ provisions Key Vaults with enablePurgeProtection: true and 90-day soft-delete retention. Some tenants have stricter retention (180 / 365 days).

Check:

az policy assignment list --subscription <SUB_ID> \
  --query "[?contains(displayName, 'Key Vault') || contains(displayName, 'soft-delete') || contains(displayName, 'purge')].{name:displayName, enforcement:enforcementMode}" \
  -o table

Pass: policy allows >= 90 days soft-delete retention. Stricter is fine.

Fail: none expected — Key Vault compliance policies typically only require purge protection to be on, which matches.


3. Quotas

3.1 Container Apps, PostgreSQL, and Managed Identity quotas in the target region

# Container Apps environments — default quota is 15 per subscription, 5 per region
az rest --method GET \
  --uri "https://management.azure.com/subscriptions/<SUB_ID>/providers/Microsoft.App/locations/eastus2/usages?api-version=2024-03-01" \
  --query "value[].{name:name.value, current:currentValue, limit:limit}" -o table

# PostgreSQL Flexible Server quota — GeneralPurpose vCores
az postgres flexible-server list-skus --location eastus2 --query "[].name" -o tsv | head -5

Pass: at least 1 Container Apps environment slot free, 1 PG Flexible Server slot free, 8 vCores of GeneralPurpose quota.

Fail: file a quota increase at least 3 business days ahead — Microsoft often takes 1–3 business days to approve PG quota in East US 2.

3.2 Multi-Zone HA quota (action required for Professional, Enterprise, Enterprise Plus)

Starter installs ship with HA = Disabled (single-AZ) by design — that tier is for evaluation / non-production work and trades resilience for cost. Professional, Enterprise, and Enterprise Plus ship with HA = ZoneRedundant, which requires Multi-Zone HA quota on the subscription in the chosen region.

If you're installing the Starter tier: no action needed from your team.

If you're installing Professional or higher and your subscription has not yet been granted Multi-Zone HA quota in the chosen region, the PostgreSQL deploy will fail until Azure approves the quota request — file the request at least 3 business days ahead. Capacity-constrained regions may take longer; if quota is declined, switch to a different region or temporarily install Starter while the quota clears.

Check: confirm the chosen region exposes multiple zones to your subscription:

az vm list-skus --location <REGION> --resource-type virtualMachines \
  --query "[?name=='Standard_D2s_v5'].locationInfo[0].zones" -o tsv | tr '\t' '\n' | sort -u

Pass: at least zones 1, 2, 3 returned. If the command returns nothing, your subscription does not see availability zones in this region — escalate to your Microsoft account team or switch regions.


4. Outbound networking

4.1 Hostnames reachable from the AltitudeIQ resource group

During bootstrap the reconciler pulls container images from GHCR, registers with the AltitudeIQ Control Plane, and streams telemetry:

Hostname Port Why
ghcr.io 443 Image pull during bootstrap + updates
*.pkg.github.com 443 GHCR layer storage (redirect target for ghcr.io)
cp.altitudeiq.ai 443 Control Plane registration, heartbeat, telemetry
*.azurecr.io (customer's own) 443 Internal — images land here after GHCR import
*.vault.azure.net (customer's own) 443 Internal — reconciler reads its secrets
*.postgres.database.azure.com (customer's own) 5432 Internal — app → DB
*.blob.core.windows.net (customer's own) 443 Internal — app → object storage
*.azurecontainerapps.io (customer's own) 443 Internal — backend ↔ frontend ↔ matching-service
dc.services.visualstudio.com 443 Application Insights ingestion
*.applicationinsights.azure.com 443 Application Insights (regional)
*.in.applicationinsights.azure.com 443 Application Insights ingestion (regional)

Check: if you use Azure Firewall / NSG-based outbound filtering, confirm the hostnames above are allowlisted. The *.altitudeiq.ai and ghcr.io entries are the only non-Azure destinations.

How to self-verify (recommended). A laptop or Azure Cloud Shell egresses from outside the VNet where AltitudeIQ will run, so it does not exercise your NSG / Azure Firewall / forced-tunnel rules. To actually test from inside the VNet, run the probe from a VM in the target subnet (or any subnet that shares the same outbound path — typical hub-and-spoke setups route every spoke through the same firewall, so any spoke works):

Step 1. Provision a throwaway Linux VM in the AltitudeIQ resource group's VNet subnet, with no public IP:

az vm create -g <RG_NAME> -n aiq-egress-probe \
  --image Ubuntu2204 --size Standard_B1ls \
  --vnet-name <VNET_NAME> --subnet <SUBNET_NAME> \
  --public-ip-address "" --admin-username probe --generate-ssh-keys

Step 2. Connect via Azure Bastion (recommended) or az ssh vm if the VNet already has a jump host:

az network bastion ssh -g <RG_NAME> --name <BASTION_NAME> \
  --target-resource-id $(az vm show -g <RG_NAME> -n aiq-egress-probe --query id -o tsv) \
  --auth-type ssh-key --username probe --ssh-key ~/.ssh/id_rsa

Step 3. From the VM, run a probe across every external hostname in the table above:

for h in ghcr.io pkg-containers.githubusercontent.com \
         cp.altitudeiq.ai \
         management.azure.com login.microsoftonline.com \
         dc.services.visualstudio.com; do
  printf '%-45s ' "$h"
  curl -sS -o /dev/null -w '%{http_code}\n' --max-time 5 "https://$h/" || echo FAIL
done

Step 4 — interpreting results. Every line should return an HTTP status code (any 2xx/3xx/4xx is fine — it means TLS completed and the request reached the host). Could not resolve host, connection timeouts, or curl: (35) SSL errors indicate the destination is being blocked by an NSG rule, Azure Firewall application rule, or upstream proxy. Cross-reference the failing hostnames against §4.2 below.

Step 5. Delete the probe VM and its associated NIC / disk (the per-resource cleanup avoids leaving orphans behind):

az vm delete -g <RG_NAME> -n aiq-egress-probe --yes
az network nic delete -g <RG_NAME> -n aiq-egress-probeVMNic 2>/dev/null || true
az disk list -g <RG_NAME> --query "[?contains(name, 'aiq-egress-probe')].name" -o tsv \
  | xargs -I{} az disk delete -g <RG_NAME> -n {} --yes

The customer-owned wildcard hostnames in the table (*.azurecr.io, *.vault.azure.net, *.postgres.database.azure.com, *.blob.core.windows.net, *.azurecontainerapps.io, *.applicationinsights.azure.com) don't exist until install completes, so they can't be tested pre-install — but if they all use Azure's Service Tags via Azure Firewall (Storage, AzureKeyVault, Sql, MicrosoftContainerRegistry), then management.azure.com succeeding from inside the VNet is a strong indicator the Service Tag routing works.

Pass: outbound HTTPS to the list above is permitted from the AltitudeIQ resource group.

Fail: file the firewall change at least 3 business days ahead. We can also run a pre-install connectivity probe from our side if you want a second confirmation before install day.

4.2 No tenant-level egress restriction that would block GHCR

Some enterprise tenants route all outbound through a tenant-level egress proxy that strips non-allowlisted TLS. If you use one, confirm ghcr.io and cp.altitudeiq.ai are in the proxy's allowlist.

Check: the ghcr.io, pkg-containers.githubusercontent.com, and cp.altitudeiq.ai rows in the §4.1 self-verify loop above cover this. If those three rows return an HTTP status (any 2xx/3xx/4xx) from inside the VNet, you're cleared. If they FAIL while Azure-owned hostnames in the same loop succeed, your egress proxy is stripping them.

Fail: this is the single most common last-minute blocker. File the change ahead of time.


5. Conditional Access

5.1 The installer-running user can complete OAuth from the install workstation

The installer authenticates via az login (browser or device code). If you have a Conditional Access policy requiring a compliant device, specific location, or MFA for management plane operations, the workstation running the installer must satisfy it.

Check: on the install workstation, run:

az login
az account set --subscription <SUB_ID>
az group create --name delete-me-preinstall-probe --location eastus2 --tags temporary=true
az group delete --name delete-me-preinstall-probe --yes

Pass: all four commands succeed without MFA loops, device-compliance denials, or "AADSTS" errors.

Fail: the error codes (e.g. AADSTS53000 for non-compliant device, AADSTS50158 for MFA) tell you which policy tripped. File a temporary exception for the install workstation, or run the installer from a device that already satisfies policy.

5.2 Service-principal-free install confirmed

The installer does not require creating a service principal. It uses your interactive login for the lifetime of the install. After install, only the installer-provisioned managed identity operates on the resource group — no long-lived user credential is stored anywhere.

No action needed from your team, but confirm this with your identity team if they ask.


6. Naming + tagging conventions

6.1 Resource group naming

The installer creates a resource group named rg-{appName} where {appName} is supplied via --app-name. Set --app-name to whatever resource group naming your Azure team prefers (the installer prepends rg-).

Fail: if your naming policy requires a specific prefix or suffix (e.g. rg-prod-pharma-altitudeiq-001), please confirm ahead so we set --app-name accordingly. Length limit: 3–20 chars, lowercase, alphanumeric + hyphens.

6.2 Mandatory tags

Many enterprise policies deny resource creation without specific tags (Cost Center, Owner, Environment, Data Classification, etc.).

Check: list any Azure Policy assignments that deny based on tags:

az policy assignment list --subscription <SUB_ID> --query "[?contains(displayName, 'tag') || contains(displayName, 'Tag')].displayName" -o tsv

Pass: no tag-required deny policies, OR the required tags are documented and we can pass them as installer parameters.

Fail: send us the tag names + values; we'll plumb them through the Bicep. This is a ~5-line change on our side.


7. Backup / DR readiness

7.1 Geo-redundant backup region is accessible

Professional, Enterprise, and Enterprise Plus use geo-redundant PostgreSQL backup with 35-day retention (paired region depends on the region you chose). Starter uses locally-redundant backup with 7-day retention — single-region by design, no cross-region data movement to worry about.

No action required from your team unless you're installing Professional or higher and your tenant has policy blocking cross-region resource data residency.

Pass:

Fail (Professional and above only): if your organization requires in-region backup only, flag ahead — we can switch to locally-redundant backup at install time, or you can install the Starter tier instead.

7.2 Subscription or RG delete-lock policy

Some enterprise subscriptions auto-apply CanNotDelete locks at the RG level. This doesn't block install, but it does prevent the teardown-and-retry flow if something goes wrong on install day.

Check:

az lock list --subscription <SUB_ID> -o table

Pass: no locks on the (yet-to-be-created) RG name. Locks at subscription-level that permit write-and-create are fine.

Fail: if the lock will be applied automatically post-creation, let us know so we can plan the mitigation path (usually: install, then lock, then re-installs need the lock lifted first).


8. SAP / Oracle connectivity (if either data source is in scope for v1)

Covered in the main customer guide section "Data Source Connectivity". Confirm the following ahead of install day:

8.1 SAP ECC / S/4HANA

8.2 Oracle

8.3 Target systems not in scope for v1

If either SAP or Oracle is out of v1 scope, skip that subsection. Can be added later — both integrations are additive.


9. Day-of-install window

9.1 Expected duration: 30–45 minutes

Phase Expected time
Installer bootstrap Bicep 10–15 min (PG Flexible Server is the long pole)
Reconciler provisions application layer 10–15 min
Health verification (5 checks) <5 min

Fail modes during the window are usually:

9.2 Rollback plan

If the install fails unrecoverably, teardown is az group delete --name rg-<appName> --yes. Destructive; cleans up everything the installer created. No customer data risk because no customer data has been loaded yet.

Retry after teardown is safe — the reconciler has a self-heal path that handles Azure's 90-day Key Vault soft-delete recovery cleanly, so a same-day retry with the same --app-name works without intervention.


10. Green-light signal

Once items 1.1 through 4 pass, your team can green-light the install window. Items 5, 6, 7.2, 8 are common tripwires — resolve those before install day, not during it. Item 7.1 is informational.

Confirmation back to AltitudeIQ on the day of:

We'll be on call during the install window to troubleshoot any surprises.