Deploying to Azure App Service

A Power Portals Pro portal is a single ASP.NET Core application whichever stack you chose, so deploying it is an ordinary dotnet publish to an ordinary App Service. This page covers the resources to create, the settings the portal reads, what we suggest as defaults, and the handful of steps that are specific to Power Portals Pro rather than to ASP.NET Core.

What you are deploying

Both templates produce one web application and one publish output. There is no separate front-end host to stand up, no static-site resource, and no second App Service:

Note

Because the React publish shells out to npm, Node has to be installed on whatever machine runs dotnet publish — your laptop, or the build agent. The template's client asks for Node ^20.19.0 || >=22.12.0. App Service itself never needs Node; it only ever sees the built output.

Suggested defaults

Starting points, not rules — but these are what we would pick for a new production portal.

1. Create the App Service

Three commands. Substitute your own names — myportal is used throughout this page.

The web app name becomes https://myportal.azurewebsites.net and has to be globally unique. Choose it deliberately: unless you put a custom domain in front, it is the URL your licence key gets bound to.

2. Set the platform options

These are the ones whose defaults are wrong for a portal.

3. Configuration

App Service application settings arrive as environment variables and override appsettings.json, which is exactly what you want: leave the file alone and set the per-environment values here.

Important

User secrets do not travel. Everything you stored with dotnet user-secrets during development lives only on your machine. Every one of those keys has to be recreated as an application setting — or a Key Vault reference — or the deployed app will not start.

Use a double underscore for the section separator: D365:ClientId becomes D365__ClientId. The colon form works on Windows App Service but not on Linux, so __ is the form to use everywhere.

Always required

Required if you enabled that sign-in provider

Optional

Note

The required keys are read with GetRequiredValue, so a missing one throws during startup rather than failing later at the first Dataverse call. On App Service that shows up as a site that never comes up — read the log stream, and the exception names the key.

4. Give the portal an address to send from

D365__EmailSenderEmailAddress is what account-confirmation and password-reset mail comes from, and the framework resolves it by looking for a Dataverse user with that address, then a queue, then a team. Use a queue. It is the one option that needs no work in Exchange at all: the mailbox Dataverse creates for a queue is pointed at the organisation’s existing Exchange Online profile, so there is no mailbox to provision and no licence to buy for the address. Pointing the setting at a real person instead costs you three things:

Three steps, all inside Dataverse. The MCP server does the first two in one call with create_email_queue:

  1. Create a private queue with the address (e.g. noreply@contoso.com), incoming delivery None and outgoing delivery Server-Side Synchronization. The queue’s name is what recipients see as the sender, so name it for the portal rather than for a person.
  2. Approve the address. Two flags have to line up and they live on different records: the queue’s own email-address approval, and the mailbox’s approved by O365 admin. A queue is created pending on both.
  3. Test & Enable the mailbox under Power Platform admin centre → Settings → Email configuration → Mailboxes. This is the one step that has to be done by hand, and it is a single button.

Important

An unapproved or untested mailbox fails silently. Dataverse accepts the email, records it, and never delivers it — which from the portal’s side is indistinguishable from a successful send. Nothing throws, nothing is logged as an error, and the first sign of trouble is a user saying they never got their confirmation link. If registration emails are not arriving, check the mailbox’s outgoing status before anything else: Not Run means this step was missed.

5. Keep the secrets in Key Vault

Give the app a managed identity, grant it read access to a vault, and put each secret's URI in the app setting instead of the secret itself. App Service resolves the reference for you, and the value never appears in the configuration blade, in a script, or in a deployment log.

When a client secret expires — Entra defaults to a year — add a new version to the vault and restart the app. The app setting itself does not change.

The MCP server does this whole sequence: create_managed_identity makes a user-assigned identity, enable_managed_identity attaches it to the app, create_key_vault creates the vault and grants that identity read access, and the secret tools then accept destination: "key-vault" — which sends a freshly minted client secret from Entra straight into the vault and leaves only the reference in the app settings. The value never becomes a literal setting and never passes through anybody’s hands.

Important

A Key Vault name is reserved for 90 days after deletion. Soft delete is on by default and cannot be turned off, so deleting a vault does not release its name — recreating one with the same name inside that window needs an explicit purge, which itself needs permission and is irreversible. It is the one resource here that is not cheaply undoable, so choose the name as deliberately as you would the App Service’s.

Note

Better still, drop the Dataverse client secret altogether. Set D365__ManagedIdentityId to a managed identity’s client id and the portal authenticates to Dataverse as that identity, with no secret and therefore nothing to expire. There is no environment check and no build switch: the setting belongs in the App Service’s configuration rather than appsettings.json, so it is absent locally and present when deployed, and one build behaves correctly in both. Use a user-assigned identity — a system-assigned one is created and destroyed with the app and differs per deployment slot, so rebuilding the app mints a new identity and orphans the Dataverse application user built on the old one. The identity still needs that application user, created from its application id rather than the principal id the Azure portal shows first.

6. Publish

Publish framework-dependent, zip the output, push the zip. On a React portal the same dotnet publish also builds the SPA and stages it into wwwroot.

Zip the contents of the publish folder, not the folder itself — App Service unpacks the archive straight into the site root, so a nested top-level directory produces a site that serves nothing.

Visual Studio's Publish dialog does the same thing interactively, and a GitHub Actions or Azure Pipelines workflow does it on push. The only extra step for a React portal is a Node setup step ahead of the build, since dotnet publish shells out to npm.

7. Point the licence at the deployed URL

The website licence is bound to a URL. In the Power Portals Pro model-driven app, open (or create) the Portal Website record holding your licence key and set its URL to the production URL of the portal you just deployed — https://myportal.azurewebsites.net, or your custom domain if one is going in front of it.

Register the production URL only. Validation also accepts -dev, -test, -uat and -stage suffixed hosts, and the same four as leading subdomains, so lower environments need no separate record. The Licensing page has the full list.

Important

Do this before you send traffic. Every data-serving endpoint checks the licence, so until the record exists and matches the host, the portal renders but every data call comes back 402.

8. Add the production redirect URI

If the portal offers Microsoft sign-in, the sign-in app registration still only knows your localhost redirect URI. Add the deployed one alongside it — Entra accepts several, so the local one keeps working:

Same pattern for the other providers on their own callback paths (/signin-google, /signin-facebook). Add one per environment, and one per custom domain.

Deployment slots

A staging slot and a swap give you a warmed-up app and an instant rollback. They are the one thing genuinely worth leaving B1 for — and the reason to leave it when you need them, rather than in advance. Two things need care.

Data Protection keys are per-slot. ASP.NET Core persists the key ring under %HOME%, which each slot has its own copy of, so a swap replaces it — and every authentication cookie issued by the previous slot becomes undecryptable. Everyone is signed out. Move the key ring somewhere both slots share before you rely on swapping:

Mark the environment-specific settings as slot settings so they stay behind during a swap. The Dataverse environment URL is the one that bites: without it, swapping a staging slot that points at a sandbox promotes that sandbox connection into production.

Scaling out

Scaling out is a decision to make when you measure the need for it, not when you provision. One instance of the default tier carries a surprising amount of portal traffic.

Doing all of this from an AI agent

The Power Portals Pro MCP server can perform every step on this page. Bind the folder to a subscription once, at a terminal — the binding belongs to that folder, so several projects can target different customers at the same time, and nothing machine-wide changes:

Note

Every tool that changes anything in Azure asks you to confirm first, and tells you what it will cost before you answer — the real monthly figure for your region, with a note that retail list ignores any Enterprise Agreement or reservation you hold. Tools that only read never prompt. The server does not use the Azure CLI and never reads or changes its signed-in state.

Troubleshooting