Table of Contents

Migrate from Azure App Service (XMPro v4.x) to AKS (XMPro v5.0)

If you're currently running XMPro v4.x on Azure App Service (the Azure Terraform or legacy ARM deployment) and planning to move to v5.0 on AKS, this guide walks through a side-by-side migration: deploy the new AKS environment, copy your databases into it, let the schema migration Jobs upgrade them, reconcile them to the new environment, validate, then cut DNS over.

Both environments live in Azure, so the databases move with a single az sql db copy command, and you can run both environments in the same subscription during the transition.

Note

Infrastructure provisioning is your responsibility. This guide uses the example Terraform module's migration4.6 stages, which are templates: community-supported examples of how XMPro provisions this infrastructure, not an officially supported product. As with the AKS deployment guide, you can use them as-is, adapt them, or provision the equivalent resources your own way; you are responsible for infrastructure configuration, maintenance, and support. See the Deployment Responsibility Matrix.

App Service (v4.x) v5.0 AKS
Compute App Service Plans per product AKS cluster with containers per product - dedicated to one environment, or optionally shared across sites
Products SM, AD, DS SM + IS (identity split out), AD, DS, optional AI
Database Azure SQL Database Azure SQL Database (new server, copied databases)
Caching Azure Cache for Redis (if enabled) Optional Azure Cache for Redis, only for multi-replica products (enable_autoscale)
TLS App Service managed certificates Let's Encrypt via cert-manager at Envoy Gateway
Ingress Per-app *.azurewebsites.net + custom domains One gateway Public IP → Envoy Gateway → HTTPRoutes
Secrets Key Vault references in app settings Key Vault + External Secrets Operator
Storage / persistence App Service filesystem + storage account file shares PV/PVCs - RWX shared mounts (Azure Files) for multi-replica, e.g. the Data Protection key ring
Deployment Terraform (Azure Terraform module) or ARM templates Terraform (layered) + Helm
Multi-environment One stack per environment One cluster, many isolated sites
Important

Your data survives the migration. The databases carry everything that matters:

  • All users, roles, companies, and product registrations (SM database)
  • All applications and pages (AD database)
  • All data streams, collections, and stream host registrations (DS database)
  • AI content, if your environment runs AI and you choose to carry its database over (AI database)

The v5.0 database migration Jobs upgrade the schema from 4.x to 5.0 automatically on first deploy against the copied databases.

Caution

The side-by-side approach means your App Service environment keeps running untouched until you've validated the new one. Do not decommission anything until the post-migration validation passes.


Required Information from Your Existing Installation

Document these values from your App Service deployment before starting.

1. Azure Resource Information

Configuration Item Where to Find Example
Resource Group Name Azure Portal rg-yourco-prod
Azure Region Azure Portal australiaeast
SQL Server Name Azure Portal → SQL servers sql-yourco-prod.database.windows.net
SQL Admin Username/Password Key Vault or secure storage sqladmin / (your password)
SM / AD / DS Database Names Azure Portal → SQL databases SM, AD, DS
AI Database Name (only if your environment runs AI) Azure Portal → SQL databases AI
Per-database SQL users (if used) Your DBA / connection strings smdbuser, addbuser, dsdbuser
Current Product URLs App Services → Custom domains https://sm.xmpro.yourco.com
Key Vault Name Azure Portal → Key vaults kv-yourco-prod

2. Application Configuration

Configuration Item Where to Find Example
AD encryption key App Service config XMPRO__APPDESIGNER__ENCRYPTIONKEY (value is usually a Key Vault reference; the key itself is the KV secret xmpro--appDesigner--encryptionKey) base64 / string
Company Names SM → Companies YourCo
Stream Host Collection IDs/Secrets DS → Collections GUIDs
SMTP Configuration App Service → Configuration Server, port, credentials
Custom Environment Variables App Service → Configuration Any xm__-prefixed settings

Finding values from the Azure CLI (snapshot the AD app's settings: that's where the application configuration, including the encryption key, lives; on 4.x the SM app itself carries almost no app settings):

# Mac/Linux (bash)
az resource list --resource-group rg-yourco-prod --output table
az sql db list --resource-group rg-yourco-prod --server sql-yourco-prod --output table
az webapp config appsettings list --resource-group rg-yourco-prod --name app-yourco-ad \
  > ad-appsettings-backup-$(date +%Y%m%d).json
# Windows (PowerShell)
az resource list --resource-group rg-yourco-prod --output table
az sql db list --resource-group rg-yourco-prod --server sql-yourco-prod --output table
az webapp config appsettings list --resource-group rg-yourco-prod --name app-yourco-ad |
  Out-File "ad-appsettings-backup-$(Get-Date -Format yyyyMMdd).json"
Note

Dedicated per-database SQL users: if your 4.x products connect with dedicated users per database (e.g. smdbuser, addbuser, dsdbuser), those database-scoped users are copied across with the databases. The v5.0 application layer currently connects to all product databases with the single SQL admin login you supply in the app stage: the copied per-database users remain in place but are not used by the products.

Important

Carry over the AD encryption key. XMPro encrypts certain stored values (e.g. data-source connection strings in AD) with the App Designer encryption key. If the v5.0 deployment generates a different key than the App Service environment that wrote the data, those values can't be decrypted after migration. Capture the v4.x App Designer encryption key (above) and supply it to the v5.0 application layer rather than letting it generate a fresh one. On an Azure Terraform 4.x App Service deployment it is the XMPRO__APPDESIGNER__ENCRYPTIONKEY app setting, whose value is usually a Key Vault reference to the secret xmpro--appDesigner--encryptionKey; on other 4.x deployments look for the App Designer encryption key setting or secret. The DS encryption key lives in its own database and carries over automatically, so the AD key is the only one supplied here. If you are also carrying an AI database over, see the AI caveat in step 3.


Backup Before Migrating

Caution

Always back up before migrating. The migration copies your databases (the originals are never modified) but backups are still your recovery path.

1. Export each database to a BACPAC

# Repeat for SM, AD, DS (and AI, if your environment runs it)
az sql db export \
  --resource-group rg-yourco-prod \
  --server sql-yourco-prod \
  --name SM \
  --storage-key-type StorageAccessKey \
  --storage-key <your-storage-key> \
  --storage-uri https://<storage-account>.blob.core.windows.net/backups/SM_PreMigration.bacpac \
  --admin-user sqladmin \
  --admin-password <password>

2. Snapshot App Service configuration

# Mac/Linux (bash)
for app in app-yourco-sm app-yourco-ad app-yourco-ds; do
  az webapp config appsettings list \
    --resource-group rg-yourco-prod --name $app \
    > ${app}-settings-backup-$(date +%Y%m%d).json
done
# Windows (PowerShell)
foreach ($app in 'app-yourco-sm', 'app-yourco-ad', 'app-yourco-ds') {
  az webapp config appsettings list --resource-group rg-yourco-prod --name $app |
    Out-File "$app-settings-backup-$(Get-Date -Format yyyyMMdd).json"
}

Registry Access

Before you start, confirm you can pull the 5.0 images. A migrating installation is more likely to trip on this than a fresh one: your existing 4.x deployment pulled anonymously from xmpro.azurecr.io and has no pull secret anywhere, so nothing carries over.

read -rs GHE_PAT            # prompts, so the token never lands in shell history
printf '%s' "$GHE_PAT" | docker login containers.xmpro.ghe.com -u YOUR-GHE-USERNAME --password-stdin
docker manifest inspect {{ACR_URL}}/ad:{{VERSION}}

If either command fails, see Registry authentication in the deployment guide. Set registry_username and registry_password in the app stage's tfvars, then pass the resulting secret name to the migration stage:

image_pull_secret = "xmpro-registry"   # app stage's image_pull_secret_name output

Running the Migration

These steps use the migration4.6 example in the AKS Terraform module. It mirrors the standard layered deployment, layer 1 cluster (infra-k8s), layer 2 site infrastructure (infra), layer 3 application (app), and adds one extra migration stage that runs after the application layer to reconcile the references your copied databases carried over from the source environment. You supply a few clearly-labelled inputs and run one apply per stage: there is no SQL to write.

The apply order (matching the example's README.md) is:

infra-k8s  →  infra  →  [az sql db copy]  →  app  →  migration

The migration stage runs after app because its reconcile Job reuses the database password from the in-cluster secret the app layer materializes (there is no separate password to supply) and it needs app's dbmigrate Jobs to have settled the upgraded schema first.

1. Deploy the cluster and site infrastructure

From the migration4.6 example, apply the infra-k8s (cluster) and infra (site infrastructure) stages, following the AKS deployment guide, including its two-phase greenfield apply for infra-k8s. Each stage ships only a terraform.tfvars.example: copy it to terraform.tfvars and fill in the marked values before applying. Two adjustments for a migration:

Use a temporary DNS subzone for validation, and delegate it before layer 3. Deploy with a staging zone (e.g. staging.xmpro.yourco.com) so the new environment is fully testable while production traffic still flows to App Service. Layer 2 creates the Azure DNS zone, but you must delegate it in the parent zone (add the staging zone's NS records to xmpro.yourco.com, wherever that parent is hosted) before applying layer 3: if the parent zone is an Azure DNS zone in the same subscription, layer 2 can do this for you via manage_parent_dns_delegation = true, parent_dns_zone_name and parent_dns_zone_resource_group (see its terraform.tfvars.example). If the staging hostnames don't resolve, cert-manager's HTTP-01 challenge can't complete, the Let's Encrypt certificate stays pending, and the is/sm pods block waiting on the TLS secret.

Layer 2 creates the SQL server, but no databases. The migration example sets use_existing_databases = true in its infra stage: the server exists purely as the copy target. Your production databases are copied in under their original names in the next step, and the app and migration stages are then pointed at those names. This applies to AI as well: leave enable_ai = false in the infra stage even if you plan to carry an AI database over, because with use_existing_databases = true it creates no database and the name it emits would not match your copy. AI is enabled in the app stage instead.

Important

Apply the infra-k8s and infra stages, but stop before the app and migration stages. The databases must be copied in before app: otherwise the app stage's dbmigrate Jobs initialize the fresh empty databases instead of upgrading yours.

2. Copy your databases to the new SQL server

Azure SQL supports direct cross-server database copy. The target server admin credentials are the db_admin_username/db_admin_password you set in the infra stage. The new server lives in the new resource group the infra stage created (rg-aks-yourco-site1 in this guide, from terraform output resource_group_name), separate from your 4.x resource group. The copies keep their source names (SM, AD, DS in this guide), the app and migration stages are pointed at those names in the next steps:

# Copy production databases across, keeping the source names
az sql db copy \
  --resource-group rg-yourco-prod --server sql-yourco-prod --name SM \
  --dest-resource-group rg-aks-yourco-site1 --dest-server sql-aks-yourco-site1 --dest-name SM

az sql db copy \
  --resource-group rg-yourco-prod --server sql-yourco-prod --name AD \
  --dest-resource-group rg-aks-yourco-site1 --dest-server sql-aks-yourco-site1 --dest-name AD

az sql db copy \
  --resource-group rg-yourco-prod --server sql-yourco-prod --name DS \
  --dest-resource-group rg-aks-yourco-site1 --dest-server sql-aks-yourco-site1 --dest-name DS

Optional - the AI database. If your v4.x environment runs the AI product, copy its database the same way, then set the extra AI inputs in the app and migration stages (called out where they apply in the next two steps). Skip this if you don't run AI, which is the common case:

az sql db copy \
  --resource-group rg-yourco-prod --server sql-yourco-prod --name AI \
  --dest-resource-group rg-aks-yourco-site1 --dest-server sql-aks-yourco-site1 --dest-name AI
Note

Cross-server copy requires the servers to be in the same tenant and your identity to have access to both. If the servers are in different tenants or you prefer an offline transfer, use the BACPAC export from the backup step with az sql db import instead.

The copied databases will run under the new server's admin login. Database-scoped users carried over from the source; the dbmigrate Jobs connect with the server admin credentials from your tfvars.

A copied database also inherits the source's SKU and size, not the infra stage's db_sku_name/db_max_size_gb. Because the copies keep their own names, Terraform never manages (or tries to resize) them.

Verify the copies completed:

az sql db list --resource-group rg-aks-yourco-site1 --server sql-aks-yourco-site1 --output table
Note

With use_existing_databases = true (the migration example's default), the infra stage creates no databases of its own: only your copied databases exist on the new server, so nothing unused is left behind billing.

3. Apply the application stage

Note

The AI product is off by default (enable_ai = false) and most migrations leave it that way. There are two exceptions:

  • Your v4.x environment runs AI and you copied its database in step 2: keep reading, the optional AI inputs are covered below.
  • You want AI as a new capability, with no existing data behind it: leave it off through the migration and enable it afterwards by re-applying the app stage; see Enabling the AI Product Later in the AKS deployment guide. That path provisions a fresh AI database rather than upgrading a copied one.

Apply the app stage next, before the migration stage. On startup, its dbmigrate Jobs detect the existing 4.x schema in each copied database and upgrade it in place to 5.0, including the SM schema changes for the SM/IS split.

Copy app/terraform.tfvars.example to terraform.tfvars (it marks every migration-specific value with a REPLACE_ placeholder) and set:

# The COPIED databases from step 2 (source names)
sm_database_name = "SM"
ad_database_name = "AD"
ds_database_name = "DS"

# Product IDs/keys from the source SM database - dbo.Product (IDs) and
# dbo.ProductKey (keys) - so each deployed product matches its registered
# OIDC client. Enter the IDs in lowercase.
sm_product_id  = "<source SM product id, lowercase>"
sm_product_key = "<source SM product key>"
ad_product_id  = "<source AD product id, lowercase>"
ad_product_key = "<source AD product key>"
ds_product_id  = "<source DS product id, lowercase>"
ds_product_key = "<source DS product key>"

# Carry over the AD encryption key so migrated AD can decrypt its existing
# data-source connection strings. AD reads it at startup and never stores it,
# so it must be set before the first apply. (DS/AI keys live in their own
# databases and carry over automatically - only the AD key is supplied here.)
ad_encryption_key = "<your v4.x XMPRO__APPDESIGNER__ENCRYPTIONKEY value>"

Capture the product IDs and keys from the source environment (the IDs must be entered in lowercase: 5.0 validates the OIDC audience case-sensitively). Two ways to get them:

Option A - from the SM UI (no SQL needed). Log in to the source SM as the Site Admin and open Products in the left navigation. Click each product: the detail panel shows a Product ID field with a copy button; the Product Keys toolbar button opens the key list. Capture the ID and key for each product you're migrating.

Note

The products are listed under their full names: XMPro is the Subscription Manager (its ID/key go into sm_product_id/sm_product_key), App Designer → ad_product_id/ad_product_key, Data Stream Designer → ds_product_id/ds_product_key. An AI entry appears only if the source environment runs AI → ai_product_id/ai_product_key.

Option B - query the source SM database:

SELECT
    p.Name AS ProductName,
    LOWER(CONVERT(varchar(36), p.Id)) AS ProductId,
    pk.[Key] AS ProductKey
FROM dbo.Product p
JOIN dbo.ProductKey pk ON pk.ProductId = p.Id
ORDER BY p.Name;

Run it against the source SM database. Save the query above as product-ids.sql, then use sqlcmd:

sqlcmd -S sql-yourco-prod.database.windows.net -d SM -U sqladmin -P '<password>' -C -i product-ids.sql

-C trusts the server certificate (needed by the encrypt-by-default sqlcmd in ODBC 18 / go-sqlcmd; omit it on older builds). If you'd rather not use the CLI, run the same query in SQL Server Management Studio or the Azure Portal → SQL databases → Query editor.

Note

Azure SQL blocks connections from your workstation by default, any direct query in this guide (sqlcmd or SSMS) needs a temporary firewall rule for your public IP on the SQL server first:

az sql server firewall-rule create --resource-group <server-rg> --server <sql-server-name> \
  --name workstation-tmp --start-ip-address <your-public-ip> --end-ip-address <your-public-ip>

Delete the rule when you're done (az sql server firewall-rule delete ... --name workstation-tmp). The Azure Portal Query editor manages this for you with a one-click prompt instead.

Expected output: one row per product, GUIDs lowercase (product names as they appear in a 4.x database; XMPro = the SM product):

ProductName            ProductId                             ProductKey
---------------------  ------------------------------------  ------------------------------------
App Designer           a1b2c3d4-1111-2222-3333-444455556666  f0e1d2c3-aaaa-bbbb-cccc-ddddeeeeffff
Data Stream Designer   b2c3d4e5-2222-3333-4444-555566667777  0f1e2d3c-bbbb-cccc-dddd-eeeeffff0000
XMPro                  c3d4e5f6-3333-4444-5555-666677778888  1a2b3c4d-cccc-dddd-eeee-ffff00001111

Extract the AD encryption key from the appsettings snapshot you took in the backup step (this is the XMPRO__APPDESIGNER__ENCRYPTIONKEY app setting on an Azure Terraform 4.x deployment):

# Mac/Linux (bash)
jq -r '.[] | select(.name == "XMPRO__APPDESIGNER__ENCRYPTIONKEY") | .value' \
  app-yourco-ad-settings-backup-<date>.json
# Windows (PowerShell)
(Get-Content app-yourco-ad-settings-backup-<date>.json | ConvertFrom-Json) |
  Where-Object name -eq 'XMPRO__APPDESIGNER__ENCRYPTIONKEY' |
  Select-Object -ExpandProperty value

If the value comes back as a Key Vault reference (it starts with @Microsoft.KeyVault(), the real key is the referenced secret, fetch it from the source Key Vault instead:

az keyvault secret show --vault-name <source-kv> \
  --name xmpro--appDesigner--encryptionKey --query value -o tsv

Optional - carrying over the AI database. Only if you copied an AI database in step 2. Uncomment the AI block at the end of app/terraform.tfvars.example and set:

enable_ai = true

# The COPIED AI database (source name), not a name generated by the infra stage
ai_database_name = "AI"

# From the same source SM query as the other products - the row named "AI".
# Enter the ID in lowercase.
ai_product_id  = "<source AI product id, lowercase>"
ai_product_key = "<source AI product key>"

The app stage then runs an ai-dbmigrate Job that upgrades the copied AI database in place, exactly as it does for SM/AD/DS, and the ai. hostname starts routing.

The RAG feature is separate and optional on top of this: setting enable_ai_rag = true also requires ai_timescaledb_connection_string and ai_neo4j_connection_string, and Terraform fails the plan if either is missing. Those services aren't part of the migration path, deploy them first with the infra-ai template as described in Enabling the AI Product Later. Leaving enable_ai_rag = false needs no connection strings.

Important

LLM provider credentials don't survive the AI carry-over. AI encrypts stored LLM provider credentials (API keys, secrets) with an infrastructure key that the app stage generates fresh on every deployment, and there is no input to supply the source environment's key. Everything else in the AI database carries over: re-enter the LLM provider credentials in AI once the new environment is up.

Then apply:

cd examples/migration4.6/app
terraform apply

Watch the dbmigrate Jobs complete. The site's Kubernetes namespace is the name_suffix you set in the app stage, site1 throughout this guide:

kubectl get jobs -n site1
kubectl logs -n site1 job/xmpro-sm-dbmigrate --tail=50
kubectl logs -n site1 job/xmpro-ad-dbmigrate --tail=50
kubectl logs -n site1 job/xmpro-ds-dbmigrate --tail=50
kubectl logs -n site1 job/xmpro-ai-dbmigrate --tail=50   # only when enable_ai = true
Note

Schema migrations are cumulative: the Jobs apply every migration between your source version and 5.0 in sequence. For large databases this takes longer than a fresh install. If a Job hits its backoff limit, check its logs before re-running; contact XMPro support if a specific migration step fails repeatedly.

4. Run the migration stage

With the app deployed, apply the migration stage. It runs a Kubernetes Job that connects to your copied databases and reconciles everything still pointing at the old environment, automatically, with no SQL to write:

  • SM - rewrites each product's Product URL / Log Out URL (App Designer, Data Stream Designer, and AI when its URL is supplied), and aligns the SM ProductKey row with the key this deployment presents (the sm_product_key you set in step 3)
  • AD - rewrites the server URL reference in XMGlobalSetting
  • DS - rewrites the server URL references in ServerVariables

This is what makes OIDC sign-in to the products succeed instead of failing with "Access Denied".

The stage runs after the app stage and reuses the app's in-cluster secret for the database password (and its image pull secret): no passwords are entered here. Copy migration/terraform.tfvars.example to terraform.tfvars and set:

sql_server_fqdn   = "<new-sql-server>.database.windows.net"   # from the infra stage
db_admin_username = "xmadmin"

# The COPIED databases from step 2 (source names)
sm_database_name = "SM"
ad_database_name = "AD"
ds_database_name = "DS"

# Must match the app stage's name_suffix - the Job runs in the app's namespace
namespace = "site1"

ad_url = "https://ad.staging.xmpro.yourco.com"
ds_url = "https://ds.staging.xmpro.yourco.com"

# Optional - uncomment only when the app stage was applied with enable_ai = true.
# Left empty (the default), the Job skips AI entirely.
# ai_url = "https://ai.staging.xmpro.yourco.com"

There is no AI database name here: the reconcile Job only rewrites SM's record of the AI product URL, it doesn't touch the AI database itself.

cd examples/migration4.6/migration
terraform apply
Note

The migration stage operates only on your copied databases (it never touches the source environment) and is safe to re-run if you change hostnames. It rewrites only XMPro's own App Designer / Data Stream Designer URL references, leaving any third-party URLs (e.g. cloud-service endpoints) untouched.

Tip

If OIDC sign-in to App Designer or Data Stream Designer still returns "Access Denied" after the migration stage, the most common cause is a hostname that changed after the migration stage ran. Re-apply the migration stage with the corrected hostnames, then restart the affected pods. As a last-resort manual check you can log in to the new SM with your existing admin credentials → menu (☰) → Products and confirm the Product URL / Product Log Out URL for each product match your new hostnames.

5. Upgrade and reconnect Stream Hosts

v4.x stream hosts are not compatible with the v5.0 platform. Replace each one with a v5.0 Stream Host, reusing the same Collection ID and Secret as before: these carried over in the DS database, so existing data stream bindings reconnect automatically.

  • In the cluster (including stream hosts that ran as Azure Container Instances from the App Service deployment): deploy through the stream-hosts layer, one Helm release per Collection, with ds_server_url pointing at the new DS hostname.
  • Outside the cluster (Windows services on plant networks, for example): install the v5.0 stream host (download link is surfaced in DS → Stream Hosts), configure the same Collection ID and Secret, and point its server URL at the new DS hostname.

Confirm each host shows online in the new DS.

Important

The default in-cluster stream host needs credentials from your migrated DS. The AKS app stage deploys one by default (enable_default_stream_host = true), so also set stream_host_collection_id and stream_host_collection_secret in the app tfvars to a collection that exists in the migrated DS database (DS → Collections shows each collection's ID and secret). Left empty, the deployment generates fresh credentials that the migrated DS rejects: the stream host crash-loops with Unauthorized, and because the Helm release is atomic, the whole app apply fails with it.

6. Validate, then cut over DNS

Run the full post-migration validation below against the staging hostnames. When everything passes:

  1. Update your production DNS records (sm., ad., ds.) to point at the new gateway Public IP (or re-deploy layers 2-3 with the production dns_zone_name and delegate it)
  2. Lower the record TTLs ahead of cutover (e.g. 300s) so rollback is fast
  3. After cutover, monitor the new environment for an agreed soak period before decommissioning

Post-Migration Validation

Validate by using the platform: log in and exercise your real applications and streams.

  • Log in at https://sm.<zone> with pre-migration credentials - users carried over in the SM database
  • Companies and roles - spot-check in SM that companies, users, and role assignments match the App Service environment
  • Applications - open your most important applications in AD; verify pages render and data connections work
  • Data streams - open existing streams in DS; verify canvases, agent configurations, and run state
  • Stream hosts - all hosts online, streams executing, data flowing end to end
  • Email - trigger a password reset for a quick SMTP end-to-end check
  • Integrations - confirm any agents using Azure resources (Storage, Event Hubs, etc.) still have network reachability from the new VNet
  • AI (only if you carried an AI database over) - open AI, confirm your existing content is there, and re-enter the LLM provider credentials

Common Issues

OIDC sign-in to App Designer or Data Stream Designer returns "Access Denied" after migration. → A product hostname changed after the migration stage ran. Re-apply the migration stage with the corrected ad_url/ds_url, then restart the affected pods. (See the migration-stage TIP.)

A dbmigrate Job hits its backoff limit or never completes. → Schema migrations are cumulative and take longer on large databases. Check the Job's logs (kubectl logs -n <name_suffix> job/xmpro-sm-dbmigrate) before re-running; contact XMPro support if a specific step fails repeatedly.

The Let's Encrypt certificate stays pending and the is/sm pods won't start. → The staging hostnames aren't resolving publicly, so cert-manager's HTTP-01 challenge can't complete. Delegate the staging subzone (or add its A records) and confirm dig ad.<zone> +short returns the gateway Public IP before re-applying the app stage.

Login works, but App Designer data sources fail to decrypt or connect. → The AD encryption key wasn't carried over: a freshly generated key can't decrypt values written by the v4.x environment. Set ad_encryption_key to the source App Designer encryption key (the XMPRO__APPDESIGNER__ENCRYPTIONKEY value) in the app stage and re-apply; it must be supplied before AD first starts.

AI was carried over, but its LLM provider calls fail with authentication errors. → Expected: the stored provider credentials were encrypted with the source environment's infrastructure key, and the app stage generates a fresh one. Re-enter the LLM provider credentials in AI. (See step 3.)

AD and DS log in fine, but Subscription Manager shows "We could not grant you access to the requested subscription". → The app stage was applied with sm_product_id left at its default instead of the source environment's SM product id, so SM's OIDC client looks for a product entry the copied database stores under a different id (the migration Job also logs SKIP: SM ProductKey (no Product row ...)). Set all product IDs and keys from the source database as described in step 3 and re-apply the app and migration stages.

Rollback (if needed)

The App Service environment was never modified, so rollback is a DNS operation:

  1. Revert the DNS records to the App Service targets
  2. Users are back on the old environment within the record TTL
Caution

Any data created in the new environment after cutover lives only in the new databases. If you must roll back after running in production, plan a reverse data reconciliation before re-cutting over, or accept the gap. This is the strongest argument for a generous validation period before decommissioning App Service.

When you're confident in the new environment, decommission the App Service stack with its Terraform (terraform destroy in the legacy configuration) or by deleting its resource group. The new environment lives entirely in its own resource groups, so removing the 4.x resource group cannot touch it.

Tip

Need assistance? Contact XMPro Support or your Implementation Partner for help with migration planning, schema upgrade validation, or rollback procedures.


Last modified: September 28, 2026